//
//  SSFileManager.swift
//  SplendidSwift
//
//  Created by WangYao on 2023/3/14.
//

import Foundation

public enum AppDirectories: String {
    case Documents = "Documents"
    case Library = "Library"
    case Temp = "tmp"
}

public protocol AppDirectoryNames {
    
    func documentsDirectoryURL() -> URL
    
    func libraryDirectoryURL() -> URL
    
    func tempDirectoryURL() -> URL
    
    func getURL(for directory: AppDirectories) -> URL
}

public protocol AppFileManipulation: AppDirectoryNames {
    
    func writeFile(containing: Data, to path: AppDirectories, withName name: String) -> Bool
    
    func readFile(at path: AppDirectories, withName name: String) -> Data?
    
    func deleteFile(at path: AppDirectories, withName name: String) -> Bool
    
    func renameFile(at path: AppDirectories, with oldName: String, to newName: String) -> Bool
    
    func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool
    
    func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool
}

public protocol AppFileStatusChecking {
    
    func isWritable(file at: URL) -> Bool
    
    func isReadable(file at: URL) -> Bool
    
    func exists(file at: URL) -> Bool
}


extension AppDirectoryNames {
    
    public func documentsDirectoryURL() -> URL {
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    }
    
    public func libraryDirectoryURL() -> URL {
        return FileManager.default.urls(for: FileManager.SearchPathDirectory.libraryDirectory, in: .userDomainMask).first!
    }
    
    public func tempDirectoryURL() -> URL {
        return FileManager.default.temporaryDirectory
    }
    
    public func getURL(for directory: AppDirectories) -> URL {
        switch directory {
        case .Documents:
            return documentsDirectoryURL()
        case .Library:
            return libraryDirectoryURL()
        case .Temp:
            return tempDirectoryURL()
        }
    }
}

extension AppFileManipulation {
    
    public func writeFile(containing: Data, to path: AppDirectories, withName name: String) -> Bool {
        let filePath = getURL(for: path).appendingPathComponent(name).path
        return FileManager.default.createFile(atPath: filePath, contents: containing, attributes: nil)
    }
    
    public func readFile(at path: AppDirectories, withName name: String) -> Data? {
        let filePath = getURL(for: path).appendingPathComponent(name).path
        return FileManager.default.contents(atPath: filePath)
    }
    
    public func deleteFile(at path: AppDirectories, withName name: String) -> Bool {
        let filePath = getURL(for: path).appendingPathComponent(name)
        return trydo {
            try FileManager.default.removeItem(at: filePath)
        }
    }
    
    func deleteFolder(at path: AppDirectories, withName name: String) -> Bool {
        
        let filePath = getURL(for: path).appendingPathComponent(name)
        
        return trydo {
            
            try FileManager.default.contentsOfDirectory(at: filePath, includingPropertiesForKeys: nil, options: []).forEach({
                try FileManager.default.removeItem(at: $0)
            })
        }
    }
    
    public func renameFile(at path: AppDirectories, with oldName: String, to newName: String) -> Bool {
        let oldPath = getURL(for: path).appendingPathComponent(oldName)
        let newPath = getURL(for: path).appendingPathComponent(newName)
        return trydo {
            try FileManager.default.moveItem(at: oldPath, to: newPath)
        }
    }
    
    public func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool {
        
        let originURL = getURL(for: inDirectory).appendingPathComponent(name)
        let destinationURL = getURL(for: directory).appendingPathComponent(name)
        return trydo {
            try FileManager.default.moveItem(at: originURL, to: destinationURL)
        }
    }
    
    public func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool {
        let originURL = getURL(for: inDirectory).appendingPathComponent(name)
        let destinationURL = getURL(for: directory).appendingPathComponent(name)
        return trydo {
            try FileManager.default.copyItem(at: originURL, to: destinationURL)
        }
    }
}

extension AppFileStatusChecking {
    
    public func isWritable(file at: URL) -> Bool {
        return FileManager.default.isWritableFile(atPath: at.path)
    }
    
    public func isReadable(file at: URL) -> Bool {
        return FileManager.default.isReadableFile(atPath: at.path)
    }
    
    public func exists(file at: URL) -> Bool {
        return FileManager.default.fileExists(atPath: at.path)
    }
}



public struct SSAppFileSystemDirectory: AppFileManipulation, AppFileStatusChecking {
    
    public let fileDirectory: AppDirectories
    
    public init(using directory: AppDirectories) {
        self.fileDirectory = directory
    }
    
    @discardableResult
    public func writeFile<T: Codable>(_ object: T, name: String) -> Bool {
       
        guard let encoded = try? JSONEncoder().encode(object) else {
            return false
        }
        return writeFile(containing: encoded, to: fileDirectory, withName: name)
    }
    
    public func readFile<T: Codable>(ofType type: T.Type, withName name: String) -> T? {
       
        guard let data = readFile(at: fileDirectory, withName: name) else {
            return nil
        }
        return try? JSONDecoder().decode(type, from: data)
    }
    
    public func deleteFile(withName name: String) -> Bool {
        return deleteFile(at: fileDirectory, withName: name)
    }
    
    public func deleteFolder(withName name: String) -> Bool {
        return deleteFolder(at: fileDirectory, withName: name)
    }

    public func existsFile(withName name: String) -> Bool {
        let fileUrl = getURL(for: fileDirectory).appendingPathComponent(name)
        return exists(file: fileUrl)
    }
}

